package edu.pg.ipse.kb.frame.kb;

import edu.pg.ipse.kb.frame.core.Constants;
import edu.pg.ipse.kb.frame.core.Context;
import edu.pg.ipse.utils.ExpressionParser;
import edu.pg.ipse.utils.MathUtils;
import edu.pg.ipse.utils.Utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.MapContext;

/**
 *
 */
public class DefaultFrameAccessObject implements IFrameAccessObject {

    private final Map<Object, Frame> frames = new HashMap<Object, Frame>();

    public DefaultFrameAccessObject() {
        buildKnowledgeBase();
    }

    public Frame getFrame(Object key) {
        if (key != null) {
            Frame frame = frames.get(key);
            frame.setFrameAccessObject(this);
            return frame;
        } else {
            return null;
        }
    }

    public List<Frame> getFrames() {
    	return new ArrayList<Frame>(frames.values());
    }
    
    private void buildKnowledgeBase() {

        // Характеристики параллельной реализации
        Frame parallelImplCharacteristicsFrame = createParallelImplCharacteristicsFrame();
        frames.put(parallelImplCharacteristicsFrame.getId(), parallelImplCharacteristicsFrame);

        // Параллельная реализация сумматора
        Frame summParallelImplFrame = new Frame(2, "Параллельная реализация сумматора");
        Slot charSlot = new Slot("Характеристики");
        charSlot.setType(SlotType.FRAME);
        charSlot.setValue(parallelImplCharacteristicsFrame);
        summParallelImplFrame.addSlot(charSlot);
        frames.put(summParallelImplFrame.getId(), summParallelImplFrame); 

        // Закон Амдала
        Frame amdahlLawFrame = createAmdahlLawFrame();
        frames.put(amdahlLawFrame.getId(), amdahlLawFrame);

        // Эффективное выполнение
        Frame effectiveExecutionFrame = createEffectiveParallelExecutionFrame();
        frames.put(effectiveExecutionFrame.getId(), effectiveExecutionFrame);
    }

    private Frame createParallelImplCharacteristicsFrame() {

        Frame charParallelImpl = new Frame(1, "Характеристики параллельной реализации");

        Slot speedUp = new Slot("Ускорение");
        Demon speedUpDemon = new Demon() {

            public Object execute(List<Object> params) {

                Double p = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.PROCESSERS_COUNT);
                Double n = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.ARRAY_LENGTH);
                
                // check predefined Speedup function
                if((Boolean) Context.getInstance().getParameter(Constants.Parameters.CUSTOM_MODE)) {
                	
                	Object inputSpeedup = 
                		Context.getInstance().getParameter(Constants.Parameters.SPEEDUP_EXPR);
	                if(inputSpeedup != null) {
	                	String speedupExpression = (String) inputSpeedup;
	                	
	                	JexlContext jc = new MapContext();
	                	jc.set("n", n);
	                	jc.set("p", p);
	                	
	                	Object result =  
	                			ExpressionParser.getInstance().evaluate(speedupExpression, jc);
	                	Utils.printOnScreen("Ускорение (S): " + speedupExpression + 
	                			" = " + result + "\n");
	                	return result;
	                }
                }
                
                double pReq = n / MathUtils.log(n.doubleValue(), 2);
                double sReg = (n - 1) / (2 * MathUtils.log(n.doubleValue(), 2));
                
                Double s;
                if(p.intValue() > pReq) {
                    s = new Double(sReg);
                } else {
                    s = new Double(sReg * p.doubleValue() / pReq);
                }
                return s;
            }
        };
        speedUp.addDemon(speedUpDemon);
        charParallelImpl.addSlot(speedUp);
        
        Slot effSlot = new Slot("Эффективность использования процесоров");
        Demon effDemon = new Demon() {

            public Object execute(List<Object> params) {
            	
            	Double p = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.PROCESSERS_COUNT);
                Double n = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.ARRAY_LENGTH);
                
                if((Boolean) Context.getInstance().getParameter(Constants.Parameters.CUSTOM_MODE)) {
                	Object inputEff = 
                		Context.getInstance().getParameter(Constants.Parameters.EFFICIENCY_EXPR);
	                if(inputEff != null) {
	                	String effExpression = (String) inputEff;
	                	
	                	JexlContext jc = new MapContext();
	                	jc.set("n", n);
	                	jc.set("p", p);
	                	
	                	Object result =  
	                			ExpressionParser.getInstance().evaluate(effExpression, jc);
	                	Utils.printOnScreen("Эффективность (Е): " + effExpression + 
	                			" = " + result + "\n");
	                	return result;
	                } else {
	                	return null;
	                }
                } else {
                    double pReq = n / MathUtils.log(n.doubleValue(), 2);
                    double sReg = (n - 1) / (2 * MathUtils.log(n.doubleValue(), 2));

                    if(p.intValue() > pReq) {
                        return new Double(sReg / p.doubleValue());
                    } else {
                        return new Double((sReg * p.doubleValue()) / (pReq * p.doubleValue()));
                    }
                }
            }
        };
        effSlot.addDemon(effDemon);
        charParallelImpl.addSlot(effSlot);

        Slot alpha = new Slot("Доля последовательных вычислений");
        Demon alphaDemon = new Demon() {
            public Object execute(List<Object> params) {
            	
                Double p = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.PROCESSERS_COUNT);
                Double n = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.ARRAY_LENGTH);
                
                if((Boolean) Context.getInstance().getParameter(Constants.Parameters.CUSTOM_MODE)) {
                	Object inputAlpha = 
                		Context.getInstance().getParameter(Constants.Parameters.ALPA_EXPR);
	                if(inputAlpha != null) {
	                	String alphaExpression = (String) inputAlpha;
	                	
	                	JexlContext jc = new MapContext();
	                	jc.set("n", n);
	                	jc.set("p", p);
	                	
	                	Object result =  
	                			ExpressionParser.getInstance().evaluate(alphaExpression, jc);
	                	result =  
                			ExpressionParser.getInstance().evaluate("p / n", jc);
	                	Utils.printOnScreen("Последовательная часть (alpha): " + alphaExpression + 
	                			" = " + result + "\n");
	                	return result;
	                }
                }
                
                return 1/p + (MathUtils.log(p.doubleValue(), 2) / n);
            }
        };
        alpha.addDemon(alphaDemon);
        charParallelImpl.addSlot(alpha);

        Slot efficiency = new Slot("Эффективность");
        Demon efficiencyDemon = new Demon() {

            public Object execute(List<Object> params) {
                Double p = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.PROCESSERS_COUNT);
                Double n = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.ARRAY_LENGTH);
                
                if((Boolean) Context.getInstance().getParameter(Constants.Parameters.CUSTOM_MODE)) {
                	Object inputEff = 
                		Context.getInstance().getParameter(Constants.Parameters.EFFICIENCY_EXPR);
	                if(inputEff != null) {
	                	String effExpression = (String) inputEff;
	                	
	                	JexlContext jc = new MapContext();
	                	jc.set("n", n);
	                	jc.set("p", p);
	                	
	                	Object result =  
	                			ExpressionParser.getInstance().evaluate(effExpression, jc);
	                	Utils.printOnScreen("Эффективность (Е): " + effExpression + 
	                			" = " + result + "\n");
	                	return result;
	                } else {
	                	return null;
	                }
                }

                double pReq = n / MathUtils.log(n.doubleValue(), 2);
                double sReg = (n - 1) / (2 * MathUtils.log(n.doubleValue(), 2));

                if(p.intValue() > pReq) {
                    return new Double(sReg / p);
                } else {
                    return new Double(sReg / pReq);
                }
            }
        };
        efficiency.addDemon(efficiencyDemon);
        charParallelImpl.addSlot(efficiency);

        return charParallelImpl;
    }

    private Frame createAmdahlLawFrame() {

        Frame amdahlLawFrame = new Frame(3, "Закон Амдала");
        amdahlLawFrame.setDescr(
                "Иллюстрирует ограничение роста производительности " +
                "вычислительной системы с увеличением количества вычислителей. " +
                "Согласно этому закону, ускорение выполнения программы за счет " +
                "распараллеливания её инструкций на множестве вычислителей " +
                "ограничено временем, необходимым для выполнения её " +
                "последовательных инструкций");
        Slot formulaSlot = new Slot("Ограничение");
        Demon formulaDemon = new Demon () {

            public Object execute(List<Object> params) {

                Double p = (Double) Context.getInstance().
                        getParameter(Constants.Parameters.PROCESSERS_COUNT);
                Frame characteristicFrame =
                        this.slot.getFrame().getFrameAccessObject().getFrame(1);
                Double alpha = 0.0;
                for (Slot tmpSlot : characteristicFrame.getSlots()) {
                    if(tmpSlot.getName().equals("Доля последовательных вычислений")) {
                        alpha = (Double) tmpSlot.getValue();
                        break;
                    }
                }
                return new Double(1 / (alpha + (1 - alpha) / p));
            }
        };
        formulaSlot.addDemon(formulaDemon);
        amdahlLawFrame.addSlot(formulaSlot);

        return amdahlLawFrame;
    }
        
    private Frame createEffectiveParallelExecutionFrame() {
    	
        Frame effectiveExecutionFrame = new Frame(4, "Эффективное выполнение");
        effectiveExecutionFrame.setDescr(
                "В качестве критерия эффективности используется закон Амдала:" +
                "Будем считать, что вариант распараллеливания эффективным " +
                "если оценочное ускорение будет меньше ограничения по закону " +
                "Амдала менее чем в 2 раза");
        Slot effectiveExecutionSlot = new Slot("Критерий ускорения");
        effectiveExecutionSlot.setType(SlotType.BOOL);
        Demon effectiveExecutionDemon = new Demon() {

            public Object execute(List<Object> params) {

                Frame characteristicFrame =
                        this.slot.getFrame().getFrameAccessObject().getFrame(1);
                Double speedUp = 0.0;
                for (Slot tmpSlot : characteristicFrame.getSlots()) {
                    if(tmpSlot.getName().equals("Ускорение")) {
                        speedUp = (Double) tmpSlot.getValue();
                        break;
                    }
                }

                Frame amdahlLawFrame =
                        this.slot.getFrame().getFrameAccessObject().getFrame(3);
                Double amdahlSpeedUp = 0.0;
                for (Slot tmpSlot : amdahlLawFrame.getSlots()) {
                    if(tmpSlot.getName().equals("Ограничение")) {
                        amdahlSpeedUp = (Double) tmpSlot.getValue();
                        break;
                    }
                }
                
                Utils.printOnScreen("(!) Критерий ускорения: ");
                Utils.printOnScreen("Samdal[" + amdahlSpeedUp + "] / " +
                		"S[" + speedUp + "] < 2 --> ");
                
                Utils.printOnScreen(amdahlSpeedUp / speedUp < 2 ? "Удовлетворяет" : "Не удовлетворяет");
                Utils.printOnScreen("\n");

                return amdahlSpeedUp / speedUp < 2;
            }
        };
        effectiveExecutionSlot.addDemon(effectiveExecutionDemon);
        effectiveExecutionFrame.addSlot(effectiveExecutionSlot);
        
        Slot procEfficientUseSlot = new Slot("Критерий эффективного использования процесоров");
        procEfficientUseSlot.setType(SlotType.BOOL);
        Demon procEfficientUseDemon = new Demon() {
        	
        	public Object execute(List<Object> params) {

                Frame characteristicFrame =
                        this.slot.getFrame().getFrameAccessObject().getFrame(1);
                Double eff = 0.0;
                for (Slot tmpSlot : characteristicFrame.getSlots()) {
                    if(tmpSlot.getName().equals("Эффективность использования процесоров")) {
                        eff = (Double) tmpSlot.getValue();
                        break;
                    }
                }
                
                Utils.printOnScreen("(!) Критерий эффективного использования процесоров: ");
                Utils.printOnScreen("E[" + eff + "] > 0.3 --> ");
                Utils.printOnScreen(eff > 0.3 ? "Удовлетворяет" : "Не удовлетворяет");
                Utils.printOnScreen("\n");
                return eff > 0.3;
            }
        };
        procEfficientUseSlot.addDemon(procEfficientUseDemon);
        effectiveExecutionFrame.addSlot(procEfficientUseSlot);
        
        return effectiveExecutionFrame;
    }
}
